package cn.bby.query.base.util;

import cn.bby.jtk.base.exception.NotSupposedException;
import cn.bby.query.base.ann.CacheField;
import cn.bby.query.base.ann.CacheTableGroup;
import cn.bby.query.base.ann.QueryInterface;
import cn.bby.query.base.constant.ColumnType;
import cn.bby.query.base.constant.PrimaryColumnType;
import cn.bby.query.base.model.dto.ColumnMetaInfo;
import cn.bby.query.base.model.dto.InterfaceMetaInfo;
import cn.bby.query.base.model.dto.TableMetaInfo;
import cn.bby.query.base.model.req.QueryRequest;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.net.url.UrlPath;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ModifierUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 元数据构建器，用于解析接口元数据并生成结果。
 *
 * @author bby
 * @date 2023/10/10
 */
@Slf4j
@Data
@Accessors(chain = true)
public class MetadataBuilder {

    private String contextPath;
    private String applicationName;
    private String baseUrl;
    private String packagePath;

    public List<InterfaceMetaInfo> build() {
        List<InterfaceMetaInfo> interfaceMetaInfos = ClassUtil.scanPackage(packagePath)
                .stream()
                .flatMap(cls -> {
                    RequestMapping requestMapping = cls.getAnnotation(RequestMapping.class);
                    String path = UrlPath.of(contextPath, StandardCharsets.UTF_8)
                            .add(requestMapping.value()[0])
                            .build(StandardCharsets.UTF_8);

                    return Arrays.stream(cls.getDeclaredMethods())
                            .filter(method -> method.isAnnotationPresent(QueryInterface.class))
                            .map(method -> processQueryInterface(method, path));
                }).filter(Objects::nonNull)
                .collect(Collectors.toList());
        log.info("接口元数据:{}", JSONUtil.toJsonPrettyStr(interfaceMetaInfos));
        return interfaceMetaInfos;
    }


    private InterfaceMetaInfo processQueryInterface(Method method, String path) {
        // 入参和出参校验
        String methodName = method.getName();
        if (method.getParameterCount() != 1 || !QueryRequest.class.isAssignableFrom(method.getParameterTypes()[0])) {
            throw new NotSupposedException("接口参数必须是 QueryRequest，methodName = %s", methodName);
        }

        // 获取 VO 类型
        Class<?> voClass;
        try {
            voClass = (Class<?>) ((ParameterizedType) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]).getActualTypeArguments()[0];
        } catch (Exception e) {
            throw new NotSupposedException("接口返回类型必须是 CommonResult<QueryResponse<VO>>，methodName = %s", methodName);
        }

        // 校验 CacheTableGroup 注解
        CacheTableGroup cacheTableGroup = voClass.getAnnotation(CacheTableGroup.class);
        if (cacheTableGroup == null) {
            throw new NotSupposedException("接口返回类型必须包含 CacheTableGroup 注解，且必须指定表名，methodName = %s", methodName);
        }

        // 初始化接口信息
        InterfaceMetaInfo interfaceMetaInfo = new InterfaceMetaInfo();
        String primaryField = cacheTableGroup.primaryField();
        String timeField = cacheTableGroup.createTimeField();
        String mainTableName = cacheTableGroup.mainTable().name();
        if (!StrUtil.isAllNotBlank(mainTableName, primaryField)) {
            throw new NotSupposedException("CacheTableGroup 注解必须指定表名和主键字段，methodName = %s", methodName);
        }

        List<TableMetaInfo> subTables = Arrays.stream(cacheTableGroup.subTables())
                .map(tb -> new TableMetaInfo(tb.name(), tb.identityField())).toList();
        interfaceMetaInfo.setSubTables(subTables);
        Set<String> tableNames = CollStreamUtil.toSet(subTables, TableMetaInfo::getTableName);
        tableNames.add(mainTableName);
        // 字段信息处理
        List<ColumnMetaInfo> columnInfos = Arrays.stream(voClass.getDeclaredFields())
                .filter(field -> !ModifierUtil.isStatic(field)) // 排除静态字段
                .map(field -> {
                    CacheField cacheField = AnnotationUtil.getAnnotationAlias(field, CacheField.class);
                    if (cacheField != null && cacheField.ignore()) {
                        return null;
                    }
                    if (StrUtil.isBlank(cacheTableGroup.mainTable().identityField())) {
                        throw new NotSupposedException("CacheTableGroup 注解必须指定 identityField，methodName = %s", methodName);
                    }

                    String fieldName = field.getName();
                    ColumnMetaInfo columnInfo = new ColumnMetaInfo()
                            .setTableName(mainTableName)
                            .setFieldName(fieldName);

                    // 处理字段类型
                    handleFieldType(columnInfo, field);

                    if (cacheField == null) {
                        columnInfo.setLinkCols(new String[]{StrUtil.toUnderlineCase(fieldName)});
                        // 其它
                        columnInfo.setTableName(mainTableName);
                        columnInfo.setIsComplex(false);
                        columnInfo.setIsConstant(false);
                    } else {
                        // 处理 linkCol 和 table 字段
                        columnInfo.setLinkCols(cacheField.linkCol());
                        // 其它
                        columnInfo.setTableName(StrUtil.emptyToDefault(cacheField.table(), mainTableName));
                        columnInfo.setIsComplex(cacheField.complex());
                        columnInfo.setIsConstant(cacheField.constant());
                    }
                    // 创建时间字段处理
                    if (fieldName.equals(timeField)) {
                        interfaceMetaInfo.setCreateTimeColumn(columnInfo.getLinkCols()[0]);
                        interfaceMetaInfo.setCreateTimeField(fieldName);
                    } else if (fieldName.equals(primaryField)) {
                        interfaceMetaInfo.setPrimaryColumn(columnInfo.getLinkCols()[0]);
                        interfaceMetaInfo.setPrimaryField(fieldName);
                        if (Number.class.isAssignableFrom(field.getType())) {
                            interfaceMetaInfo.setPrimaryColumnType(PrimaryColumnType.NUMBER);
                        } else if (String.class.isAssignableFrom(field.getType())) {
                            interfaceMetaInfo.setPrimaryColumnType(PrimaryColumnType.STRING);
                        } else {
                            throw new NotSupposedException("主键字段类型只能是 Number 或 String，methodName = %s", methodName);
                        }
                        if (!tableNames.contains(columnInfo.getTableName())) {
                            throw new NotSupposedException("表不存在，col=", columnInfo);
                        }
                    }
                    // 子表主键
                    return columnInfo;
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        if (StrUtil.isBlank(interfaceMetaInfo.getPrimaryColumn())) {
            throw new NotSupposedException("主键字段不能为空，methodName = %s", methodName);
        }
        if (columnInfos.isEmpty()) {
            throw new NotSupposedException("没有字段信息！", methodName);
        }

        // 设置创建时间字段
        if (StrUtil.isEmpty(interfaceMetaInfo.getCreateTimeColumn())) {
            Optional<ColumnMetaInfo> any = columnInfos.stream()
                    .filter(columnInfo -> mainTableName.equals(columnInfo.getTableName()) && !columnInfo.getIsComplex() && "createTime".equals(columnInfo.getFieldName()))
                    .findAny();
            if (any.isPresent()) {
                interfaceMetaInfo.setCreateTimeField(any.get().getFieldName());
                interfaceMetaInfo.setCreateTimeColumn(any.get().getLinkCols()[0]);
            } else {
                throw new NotSupposedException("没有创建时间字段！", methodName);
            }
        }

        // 设置接口路径
        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
        String methodPath = requestMapping != null ? requestMapping.value()[0] : null;
        if (methodPath == null) {
            PostMapping postMapping = method.getAnnotation(PostMapping.class);
            if (postMapping == null) {
                throw new NotSupposedException("没有 RequestMapping 注解！", methodName);
            }
            methodPath = postMapping.value()[0];
            if (!methodPath.startsWith("/")) {
                methodPath = "/" + methodPath;
            }
        }

        // 设置接口元信息
        QueryInterface queryInterface = method.getAnnotation(QueryInterface.class);
        interfaceMetaInfo.setBaseUrl(baseUrl)
                .setInterfaceName(queryInterface.name())
                .setVersion(queryInterface.version())
                .setDescription(queryInterface.description())
                .setMethodPath(UriUtil.concatUri(path, methodPath))
                .setMainTableName(mainTableName)
                .setIsPrimaryIncrement(cacheTableGroup.isPrimaryIncrement())
                .setColumns(columnInfos);
        return interfaceMetaInfo;
    }

    private void handleFieldType(ColumnMetaInfo columnInfo, Field field) {
        Class<?> type = field.getType();
        if (type.isPrimitive()) {
            switch (field.getType().getName()) {
                case "int":
                case "byte":
                case "long":
                case "double":
                case "float":
                    columnInfo.setType(ColumnType.NUMBER.code);
                    return;
                case "boolean":
                    columnInfo.setType(ColumnType.BOOLEAN.code);
                    return;
                case "char":
                    columnInfo.setType(ColumnType.STRING.code);
                    return;
                default:
                    throw new NotSupposedException("不支持的类型：%s", field.getType().getName());
            }
        }

        if (type.isEnum()) {
            columnInfo.setType(ColumnType.STRING.code);
        } else if (type.isArray() || Collection.class.isAssignableFrom(type)) {
            columnInfo.setType(ColumnType.LIST.code);
        } else if (Number.class.isAssignableFrom(type)) {
            if (type == Integer.class || type == Long.class || type == Short.class || type == Byte.class) {
                columnInfo.setType(ColumnType.NUMBER.code);
            } else {
                columnInfo.setType(ColumnType.DECIMAL.code);
            }
        } else if (String.class.isAssignableFrom(type)) {
            columnInfo.setType(ColumnType.STRING.code);
        } else if (Date.class.isAssignableFrom(type)) {
            columnInfo.setType(ColumnType.DATE.code);
        } else if (Boolean.class.isAssignableFrom(type)) {
            columnInfo.setType(ColumnType.BOOLEAN.code);
        } else {
            throw new NotSupposedException("不支持循嵌套：%s:%s", field.getName(), field.getType().getName());
        }
    }
}